﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using System.IO;
using Microsoft.VisualStudio.Shell.Interop;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace PovTeam.Pkg_PovrayProject
{
    enum IdentifierType
    {
        Float,
        Vector,
        Color,
        String,
        Object,
        Texture,
        Pigment,
        Normal,
        Finish,
        Interior,
        Media,
        Density,
        ColorMap,
        PigmentMap,
        SlopeMap,
        NormalMap,
        DensityMap,
        Camera,
        LightSource,
        Fog,
        Rainbow,
        SkySphere,
        Transform,
        Unknown,
        Material,
        Boolean,
        Function,
        TextureMap,
        Array,
        FileIdentifier,
        Spline,
        Pattern,
        MaterialMap
    }

    [DebuggerDisplay("{Name}, {Type}")]
    class Identifier
    {

        public Identifier(string name, IdentifierType type, string desc, int icon)
        {
            Name = name;
            Type = type;
            Desc = desc;
            Icon = icon;
        }

        public string Name { get; private set; }

        public IdentifierType Type { get; set; }


        public string Desc { get; set; }

        public int Icon { get; set; }
    }

    class PovIdentifierScanner
    {

        private Pkg_PovrayProjectPackage package;
        private PovCodeWindowManager codeWindow;
        public PovIdentifierScanner(PovCodeWindowManager codeWindow)
        {
            this.package = codeWindow.Package;
            this.codeWindow = codeWindow;
            codeWindow.CodeWindow.GetBuffer(out this.buffer);
        }

        public Identifier[] GetIdentfierList(bool scanIncludes)
        {
            int nLines;
            buffer.GetLineCount(out nLines);



            List<Identifier> identifiers = new List<Identifier>();

            for (int i = 0; i < nLines; i++)
            {
                int lineLen;
                string sLine;
                buffer.GetLengthOfLine(i, out lineLen);
                buffer.GetLineText(i, 0, i, lineLen, out sLine);
                List<Identifier> identifier = ScanLine(sLine, scanIncludes, identifiers);
                if (identifier.Count == 1)
                {
                    if (identifier[0].Type == IdentifierType.Unknown)
                    { // Try the next line
                        bool done = false;
                        while (i < nLines - 1 && !done)
                        {
                            buffer.GetLengthOfLine(i + 1, out lineLen);
                            buffer.GetLineText(i + 1, 0, i + 1, lineLen, out sLine);

                            if (!(sLine.Trim().StartsWith("//") || sLine.Trim().StartsWith("/*")))
                            {
                                identifier[0].Type = FindIdentifierType(sLine.Trim(), identifiers);
                                identifier[0].Desc += "\n" + sLine.Trim();
                                done = true;
                            }

                        }
                    }
                }
                identifiers.AddRange(identifier);
            }
            return identifiers.ToArray();
        }

        private List<Identifier> ScanLine(string sLine, bool scanIncludes, List<Identifier> existingIdents)
        {
            List<Identifier> identifiers = new List<Identifier>();
            if (scanIncludes && sLine.Trim().StartsWith("#include"))
            {
                int fileStart = sLine.IndexOf('"') + 1;
                int fileEnd = sLine.IndexOf('"', fileStart);
                string fileName = sLine.Substring(fileStart, fileEnd - fileStart);
                identifiers.AddRange(ScanIncludes(fileName));
            }
            else if (sLine.Trim().Contains("#declare")) //TODO: Note: not looking for locals!
            {
                int startIndex = sLine.IndexOf("#declare") + "#declare".Length + 1;
                int endIndex = sLine.IndexOf('=', startIndex) - 1;
                if (endIndex > startIndex)
                {
                    Identifier sIdentifier = CreateIdentifier(sLine.Substring(startIndex), existingIdents.Union(identifiers).ToList());
                    identifiers.Add(sIdentifier);
                }
            }
            return identifiers;
        }

        private Identifier CreateIdentifier(string text, List<Identifier> identifiers)
        {
            int equLoc = text.IndexOf('=');

            string name = text.Substring(0, equLoc).Trim();
            IdentifierType type = FindIdentifierType(text.Substring(equLoc + 1), identifiers);

            string desc = text;

            return new Identifier(name, type, desc, 6);
        }

        private IdentifierType FindIdentifierType(string p, List<Identifier> identifiers)
        {

            p = p.Trim(); // Get rid of leading and trailing space

            if (string.IsNullOrEmpty(p)) return IdentifierType.Unknown;

            if (char.IsNumber(p[0]))
            {// Numeric value, this is a float
                return IdentifierType.Float;
            }
            else if (p[0] == '<')
            {
                return IdentifierType.Vector;
            }
            else if (p.StartsWith("density_map"))
            {
                return IdentifierType.DensityMap;
            }
            else if (p.StartsWith("color_map"))
            {
                return IdentifierType.ColorMap;
            }
            else if (p.StartsWith("normal_map"))
            {
                return IdentifierType.TextureMap;
            }
            else if (p.StartsWith("pigment_map"))
            {
                return IdentifierType.PigmentMap;
            }
            else if (p.StartsWith("slope_map"))
            {
                return IdentifierType.SlopeMap;
            }
            else if (p.StartsWith("normal_map"))
            {
                return IdentifierType.NormalMap;
            }
            else if (p.StartsWith("material"))
            {
                return IdentifierType.Material;
            }
            else if (p.StartsWith("color") ||
                     p.StartsWith("rgb") ||
                     p.StartsWith("srgb"))
            {
                return IdentifierType.Color;
            }
            else if (p.StartsWith("\""))
            {
                return IdentifierType.String;
            }
            else if (p.StartsWith("texture"))
            {
                return IdentifierType.Texture;
            }
            else if (p.StartsWith("finish"))
            {
                return IdentifierType.Finish;
            }
            else if (p.StartsWith("pigment"))
            {
                return IdentifierType.Pigment;
            }
            else if (p.StartsWith("normal"))
            {
                return IdentifierType.Normal;
            }
            else if (p.StartsWith("interior"))
            {
                return IdentifierType.Interior;
            }
            else if (p.StartsWith("media"))
            {
                return IdentifierType.Media;
            }
            else if (p.StartsWith("density"))
            {
                return IdentifierType.Density;
            }
            else if (p.StartsWith("camera"))
            {
                return IdentifierType.Camera;
            }
            else if (p.StartsWith("light_source"))
            {
                return IdentifierType.LightSource;
            }
            else if (p.StartsWith("fog"))
            {
                return IdentifierType.Fog;
            }
            else if (p.StartsWith("rainbow"))
            {
                return IdentifierType.Rainbow;
            }
            else if (p.StartsWith("sky_sphere"))
            {
                return IdentifierType.SkySphere;
            }
            else if (p.StartsWith("translate") ||
                     p.StartsWith("scale") ||
                     p.StartsWith("transform") ||
                     p.StartsWith("matrix") ||
                     p.StartsWith("rotate"))
            {
                return IdentifierType.Transform;
            }
            else if (p.StartsWith("function"))
            {
                return IdentifierType.Function;
            }
            else if (
                p.StartsWith("object") ||
                p.StartsWith("blob") ||
                p.StartsWith("box") ||
                p.StartsWith("cone") ||
                p.StartsWith("cylinder") ||
                p.StartsWith("height_field") ||
                p.StartsWith("isosurface") ||
                p.StartsWith("julia_fractal") ||
                p.StartsWith("lathe") ||
                p.StartsWith("ovus") ||
                p.StartsWith("parametric") ||
                p.StartsWith("prism") ||
                p.StartsWith("sphere") ||
                p.StartsWith("sphere_sweep") ||
                p.StartsWith("superellipsoid") ||
                p.StartsWith("sor") ||
                p.StartsWith("text") ||
                p.StartsWith("torus") ||
                p.StartsWith("bicubic_patch") ||
                p.StartsWith("disc") ||
                p.StartsWith("mesh") ||
                p.StartsWith("mesh2") ||
                p.StartsWith("polygon") ||
                p.StartsWith("triangle") ||
                p.StartsWith("smooth_triangle") ||
                p.StartsWith("plane") ||
                p.StartsWith("poly") ||
                p.StartsWith("polynomial") ||
                p.StartsWith("cubic") ||
                p.StartsWith("quartic") ||
                p.StartsWith("quadric") ||
                p.StartsWith("union") ||
                p.StartsWith("intersection") ||
                p.StartsWith("difference") ||
                p.StartsWith("merge")
                )
            {
                return IdentifierType.Object;
            }
            else if (
                p.StartsWith("abs")
               || p.StartsWith("acos")
               || p.StartsWith("acosh")
               || p.StartsWith("asc")
               || p.StartsWith("asin")
               || p.StartsWith("asinh")
               || p.StartsWith("atan")
               || p.StartsWith("atanh")
               || p.StartsWith("atan2")
               || p.StartsWith("ceil")
               || p.StartsWith("cos")
               || p.StartsWith("cosh")
               || p.StartsWith("defined")
               || p.StartsWith("degrees")
               || p.StartsWith("dimensions")
               || p.StartsWith("dimension_size")
               || p.StartsWith("div")
               || p.StartsWith("exp")
               || p.StartsWith("file_exists")
               || p.StartsWith("floor")
               || p.StartsWith("int")
               || p.StartsWith("inside")
               || p.StartsWith("ln")
               || p.StartsWith("log")
               || p.StartsWith("max")
               || p.StartsWith("min")
               || p.StartsWith("mod")
               || p.StartsWith("pow")
               || p.StartsWith("radians")
               || p.StartsWith("rand")
               || p.StartsWith("seed")
               || p.StartsWith("select")
               || p.StartsWith("sin")
               || p.StartsWith("sinh")
               || p.StartsWith("sqrt")
               || p.StartsWith("strcmp")
               || p.StartsWith("strlen")
               || p.StartsWith("tan")
               || p.StartsWith("tanh")
               || p.StartsWith("val")
               || p.StartsWith("vdot")
               || p.StartsWith("vlength")
               || p.StartsWith("clock")
               || p.StartsWith("clock_delta")
               || p.StartsWith("clock_on")
               || p.StartsWith("final_clock")
               || p.StartsWith("final_frame")
               || p.StartsWith("frame_number")
               || p.StartsWith("image_height")
               || p.StartsWith("image_width")
               || p.StartsWith("initial_clock")
               || p.StartsWith("initial_frame")
               || p.StartsWith("pi")
               || p.StartsWith("version")
)
            {
                return IdentifierType.Float;
            }
            else if (p.StartsWith("true") || p.StartsWith("false") || p.StartsWith("yes") || p.StartsWith("no"))
            {
                return IdentifierType.Boolean;
            }
            else
            {
                var matches = identifiers.Where(i => p.StartsWith(i.Name));
                if (matches.Count() > 0)
                {
                    return matches.First().Type;
                }
                return IdentifierType.Unknown;
            }

        }

        HashSet<String> scannedFiles = new HashSet<string>();

        public List<Identifier> ScanIncludes(string filename)
        {
            filename = FindExactPath(filename);
            if (filename == String.Empty) return new List<Identifier>();
            if (scannedFiles.Contains(filename)) return new List<Identifier>(); // Don't scan files that have already been scanned.
            List<Identifier> identifiers = new List<Identifier>();
            StreamReader file = File.OpenText(filename);

            bool skip = false;
            string sLine = "";
            while (!file.EndOfStream)
            {

                if (!skip) sLine = file.ReadLine();
                skip = false;
                List<Identifier> identifier = ScanLine(sLine, true, identifiers);
                if (identifier.Count == 1)
                {
                    if (identifier[0].Type == IdentifierType.Unknown)
                    { // Try the next line
                        bool done = false;
                        while (!file.EndOfStream && !done)
                        {
                            skip = true;
                            sLine = file.ReadLine();
                            if (!(sLine.Trim().StartsWith("//") || sLine.Trim().StartsWith("/*")))
                            {
                                identifier[0].Type = FindIdentifierType(sLine.Trim(), identifiers);
                                done = true;
                            }
                        }
                    }
                }
                identifiers.AddRange(identifier);
            }

            file.Close();
            scannedFiles.Add(filename);
            return identifiers;
        }

        private string FindExactPath(string filename)
        {
            if (File.Exists(filename)) return filename;

            IVsMonitorSelection monitor = package.ServiceProvider.GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;

            IntPtr pHier;
            uint itemid;
            IVsMultiItemSelect multiSel;
            IntPtr pSC;

            // This probably isn't the best place to do this....
            monitor.GetCurrentSelection(out pHier, out itemid, out multiSel, out pSC);

            IVsHierarchy hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(pHier);
            object prop;
            hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_SaveName, out prop);

            string currentFile = (string)prop;

            string folder = Path.GetDirectoryName(currentFile);

            string newFilename = Path.Combine(folder, filename);

            if (File.Exists(newFilename))
            {
                return newFilename;
            }
            else // Start scanning includes
            {
                IVsSettingsManager profile = package.ServiceProvider.GetService(typeof(SVsSettingsManager)) as IVsSettingsManager;
                IVsSettingsStore store;
                profile.GetReadOnlySettingsStore((uint)__VsEnclosingScopes.EnclosingScopes_UserSettings, out store);
                string sInc;

                store.GetStringOrDefault("DialogPage\\PovTeam.Pkg_PovrayProject.PovrayDirectoriesConfigPage", "IncludeDirectories", @"[USER]\POV-Ray\v3.7\include" ,out sInc);

                sInc = sInc.Replace("[USER]", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));

                string[] folders = sInc.Split(';');

                foreach (string incPath in folders)
                {
                    newFilename = Path.Combine(incPath, filename);
                    if (File.Exists(newFilename)) return newFilename;
                }

            }

            return String.Empty;
        }


        private IVsTextLines buffer;
    }
}
